#include "qe_log.h"
#include "qe_bitops.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-intc");



#define AXI_INTC_MASTER_ENABLE_MASK     QE_BIT(0)
#define AXI_INTC_HARDWARE_ENABLE_MASK   QE_BIT(1)
#define AXI_INTC_MAX_INTERRUPTS			(32)

typedef struct
{
    volatile qe_u32 isr;    /* 0x00  */
    volatile qe_u32 ipr;    /* 0x04 */
    volatile qe_u32 ier;    /* 0x08 */
    volatile qe_u32 iar;    /* 0x0C */
    volatile qe_u32 sie;    /* 0x10 */
    volatile qe_u32 cie;    /* 0x14 */
    volatile qe_u32 ivr;    /* 0x18 */
    volatile qe_u32 mer;    /* 0x1C */
    volatile qe_u32 imr;    /* 0x20 */
    volatile qe_u32 ilr;    /* 0x24 */
    volatile qe_u32 reserved0[54];
    volatile qe_u32 ivar;  /* 0x100~0x17C Interrupt Vector Address Register */
    volatile qe_u32 reserved1[32];
    volatile qe_u32 ivear_base;  /* 0x200~0x2FC Interrupt Vector Extended Address Register */
} axi_intc_reg;

/* Vector table entry */
typedef struct
{
    qe_irq_handler handler;
    void *args;
} axi_intc_vte;

typedef struct 
{
    qe_intc_dev intc;
    qe_intc_dev *parent;
    qe_int irq;
    qe_uint max_interrupts;
    qe_u32 bitmask[AXI_INTC_MAX_INTERRUPTS];
    qe_bool fast_mode_en;
    qe_bool is_root;
    axi_intc_reg *reg;
    axi_intc_vte vecs[AXI_INTC_MAX_INTERRUPTS];
} axi_intc_dev;



static void axi_intc_interrupt_handler(axi_intc_dev *dev);

static void axi_intc_hw_init(axi_intc_dev *dev)
{
    int i;
    qe_u32 next_bitmask = 1;

#if (CONFIG_PORT_INTERRUPT_MGMT == 1)
    if (dev->is_root) {
        return;
    }
#endif

    /* Disable IRQ output signal */
    dev->reg->mer = 0;
    dev->reg->ier = 0;
    dev->reg->iar = 0;

	/*
	 * If the fast Interrupt mode is enabled then set all the
	 * interrupts as normal mode.
	 */
    if (dev->fast_mode_en) {
        dev->reg->imr = 0;
    }

    for (i=0; i<AXI_INTC_MAX_INTERRUPTS; i++) {
        dev->reg->ivar = 0x10;
        dev->bitmask[i] = next_bitmask;
        next_bitmask *= 2;
    }

    /* Strat the master */
    dev->reg->mer = AXI_INTC_MASTER_ENABLE_MASK | AXI_INTC_HARDWARE_ENABLE_MASK;

#if (CONFIG_PORT_INTERRUPT_MGMT == 0)
    if (dev->is_root) {
        qe_intc_mount((qe_irq_handler)axi_intc_interrupt_handler, dev);
    }
#endif
}

static qe_ret axi_intc_irq_register(qe_intc_dev *dev, qe_int irq, qe_irq_handler handler, void *args)
{
	axi_intc_dev *intc = (axi_intc_dev *)dev;

#if (CONFIG_PORT_INTERRUPT_MGMT == 1)
    if (intc->is_root) {
        return qe_irq_register(irq, handler, args);
    }
#endif

    if (irq > intc->max_interrupts) {
        qe_error("invalid irq %d", irq);
        return qe_err_range;
    }

    intc->vecs[irq].handler = handler;
    intc->vecs[irq].args = args;

    return qe_ok;
}

static void axi_intc_irq_unregister(qe_intc_dev *dev, int irq)
{
	axi_intc_dev *intc = (axi_intc_dev *)dev;

#if (CONFIG_PORT_INTERRUPT_MGMT == 1)
    if (intc->is_root) {
        return qe_irq_unregister(irq);
    }
#endif

    if (irq > intc->max_interrupts) {
        qe_error("invalid irq %d", irq);
        return;
    }

    /* Disable IRQ */
    intc->reg->ier &= ~(intc->bitmask[irq]);

    intc->vecs[irq].handler = QE_NULL;
    intc->vecs[irq].args = QE_NULL;
}

static qe_ret axi_intc_irq_enable(qe_intc_dev *dev, qe_int irq)
{
    qe_u32 mask;
    axi_intc_dev *intc = (axi_intc_dev *)dev;

#if (CONFIG_PORT_INTERRUPT_MGMT == 1)
    if (intc->is_root) {
        return qe_irq_enable(irq);
    }
#endif

    if (irq > intc->max_interrupts) {
        qe_error("invalid irq %d", irq);
        return qe_err_range;
    }

    mask = intc->bitmask[irq];

    intc->reg->ier |= mask;

    return qe_ok;
}

static void axi_intc_irq_disable(qe_intc_dev *dev, int irq)
{
    qe_u32 mask;
    axi_intc_dev *intc = (axi_intc_dev *)dev;

#if (CONFIG_PORT_INTERRUPT_MGMT == 1)
    if (intc->is_root) {
        return qe_irq_disable(irq);
    }
#endif

    if (irq > intc->max_interrupts) {
        qe_error("invalid irq %d", irq);
        return;
    }

    mask = intc->bitmask[irq];

    intc->reg->ier &= ~mask;
}

static void axi_intc_interrupt_handler(axi_intc_dev *dev)
{
	int i;
    qe_u32 isr;
    qe_u32 mask = 1;
    axi_intc_vte *vec;

    /* Get the interrupts that are waiting to be serviced */
    isr = dev->reg->isr & dev->reg->ier;

    /* Mask the Fast Interrupts */
    if (dev->fast_mode_en) {
        isr &= ~dev->reg->imr;
    }

    /* Service each interrupt that is active and enabled by
     * checking each bit in the register from LSB to MSB which
     * corresponds to an interrupt input signal
     */
    
    for (i=0; i<dev->max_interrupts; i++) {

        if (isr & 1) {

            /* Call interrupt handler */
            vec = &dev->vecs[i];
            vec->handler(vec->args);

            /* Acknowledge the interrupt after service */
            dev->reg->iar = mask;

            /*
			 * Read the ISR again to handle architectures
			 * with posted write bus access issues.
			 */
            (void)dev->reg->isr;

        }

        /* Move	 to the next interrupt to check */
		mask <<= 1;
		isr >>= 1;

        /* If there are no other bits set indicating that all
         * interrupts have been serviced, then exit the loop
         */
        if (!isr)
            break;
    }
}

static qe_intc_ops axi_intc_ops = {
    .irq_register = axi_intc_irq_register,
    .irq_unregister = axi_intc_irq_unregister,
    .irq_enable = axi_intc_irq_enable,
    .irq_disable = axi_intc_irq_disable,
};

static qe_ret axi_intc_probe(const void *fdt, int offset)
{
    int len;
    const char *name;
    const char *parent;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_max;
    const qe_u32 *prop_irq;
    const qe_u32 *prop_fast;

    qelog_domain_set_level("axi-intc", QELOG_WARNING);

    name = fdt_get_name(fdt, offset, QE_NULL);
    qe_assert(name);

    prop_reg = fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    parent = qe_of_get_interrupt_parent_name(fdt, offset);

    prop_max = fdt_getprop(fdt, offset, "max-interrupts", &len);
    if (!prop_max) {
        qe_error("%s no max-interrupts prop", name);
        return qe_err_param;
    }

    prop_fast = fdt_getprop(fdt, offset, "fast-interrupt", &len);

    axi_intc_dev *dev = qe_malloc(sizeof(axi_intc_dev));
    qe_assert(dev);

    dev->reg = (axi_intc_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->max_interrupts = fdt32_to_cpu(*prop_max);
    if (prop_fast) {
        dev->fast_mode_en = fdt32_to_cpu(*prop_fast) == 1;
    } else {
        dev->fast_mode_en = 0;
    }

    if (parent) {
        
        prop_irq = fdt_getprop(fdt, offset, "interrupts", &len);
        if (!prop_irq) {
            qe_error("has parent but no interrupts");
            goto __error;
        }
        dev->irq = fdt32_to_cpu(prop_irq[1]);

        dev->parent = (qe_intc_dev *)qe_dev_find(parent);
        if (!dev->parent) {
            qe_error("parent %s not found", parent);
            goto __error;
        }

        /* Register to parent controller */
        dev->is_root = 0;
        qe_intc_irq_register(
            dev->parent, 
            dev->irq, 
            (qe_irq_handler)axi_intc_interrupt_handler, 
            dev);
    } else {
        dev->is_root = 1;
    }

    axi_intc_hw_init(dev);

    qe_debug("register %s ", name);

    qe_intc_register(&dev->intc, name, &axi_intc_ops);

    return qe_ok;

__error:
    qe_free(dev);
    return qe_err_common;
}

static const qe_device_id axi_intc_ids[] = {
    {.compatible = "xlnx,axi-interrupt-controller",},
    {},
};

QE_DRIVER(axi_intc) = {
    .name = "axi-intc",
    .of_match = axi_intc_ids,
    .probe = axi_intc_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_intc)
